package com.tiny.tinyweb.core.ldap.access;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextAdapter;

import com.tiny.tinyweb.core.exception.InvalidObjectException;
import com.tiny.tinyweb.core.ldap.entry.MapEntry;
import com.tiny.tinyweb.core.utils.ReflectionUtils;

/**
 * 将 LDAP 条目映射为持久层对象 {@link com.tiny.tinyweb.core.ldap.entry.MapEntry} 的映射器。
 * @param <T> 映射类的泛型约束
 * @see {@link org.springframework.ldap.core.ContextMapper}
 * @author wxiaobin
 */
public class EntryMapper<T extends MapEntry> implements ContextMapper {
    /**
     * 映射类。
     */
    private Class<T> mappedClass;

    /**
     * 设置映射类。
     * @param managedClass 映射类的新值
     */
    public void setMappedClass(Class<T> managedClass) {
        this.mappedClass = managedClass;
    }

    /**
     * 将 LDAP 条目映射为持久层对象，为 {@link org.springframework.ldap.core.LdapTemplate}
     * 的 <code>lookup</code>，<code>search</code> 等处理提供向对象映射的支持。
     * @param object LDAP 条目上下文环境，通常是一个
     * {@link org.springframework.ldap.core.DirContextAdapter} 对象
     * @return 映射后的持久层对象
     */
    public Object mapFromContext(Object object) {
        try {
            // 获取当前条目的所有属性
            NamingEnumeration<? extends Attribute> attrsEnum =
                    ((DirContextAdapter) object).getAttributes().getAll();
            // 将当前条目的属性包装成一个属性名为键、属性信息为值的 Map 对象
            Map<String, Attribute> attrsMap = new HashMap<String, Attribute>();
            while (attrsEnum.hasMoreElements()) {
                Attribute attrs = attrsEnum.nextElement();
                attrsMap.put(attrs.getID(), attrs);
            }

            // 实例化一个保存条目信息的对象
            T entry = this.mappedClass.newInstance();
            // 获得该条目对象的属性描述器和被包装的条目对象，用来根据属性名称设置属性值
            Map<String, PropertyDescriptor> properties = entry.getProperties();
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(entry);
            // 定义一个类型转换器，将查询到的条目信息向条目对象映射时提供类型转换支持
            SimpleTypeConverter stc = new SimpleTypeConverter();
            // 循环对象中所有定义的属性，从条目属性信息中映射相应的值
            for (Entry<String, PropertyDescriptor> property : properties.entrySet()) {
                // 获得当前属性的描述器
                PropertyDescriptor pd = property.getValue();
                // 跳过当前条目中不存在的属性
                if (attrsMap.get(pd.getName()) == null) {
                    continue;
                }
                // 获得当前属性的数据类型
                Class<?> typeClass = pd.getPropertyType();
                // 处理集合属性和非集合属性
                if (List.class.equals(typeClass)) {
                    // 如果是集合属性，获得向集合中添加项目的方法，
                    // 即名为“add+首字母大写的字段名”、只有一个参数的公开方法
                    Method method = ReflectionUtils.findMethod(
                            this.mappedClass, pd.getName(), "add", 1);
                    // 跳过不可写属性（未提供向集合中添加项目的方法，则该集合不可写）
                    if (method == null) {
                        continue;
                    }
                    // 获得 LDAP 条目中该属性的所有项目（LDAP中可以存在多个同名属性）
                    NamingEnumeration<?> valuesEnum =
                            attrsMap.get(pd.getName()).getAll();
                    // 反射调用向集合中添加项目的方法，将多值属性的值添加到对象的集合属性中
                    while (valuesEnum.hasMore()) {
                        // 根据被反射调用的方法的唯一参数的类型进行类型转换
                        method.invoke(entry, stc.convertIfNecessary(
                                valuesEnum.nextElement(),
                                method.getParameterTypes()[0]));
                    }
                } else {
                    // 如果是非集合属性，根据该属性的数据类型进行类型转换，
                    // 并通过被包装的条目对象为属性赋值
                    bw.setPropertyValue(pd.getName(), stc.convertIfNecessary(
                            attrsMap.get(pd.getName()).get(), typeClass));
                }
            }

            // 返回映射后的条目对象
            return entry;
        } catch (InvocationTargetException ite) {
            // 反射调用向集合中添加项目的方法时发生异常
            throw new InvalidObjectException(String.format(
                    "Can not invoke specified method of %1$s.",
                    this.mappedClass), ite);
        } catch (NamingException ne) {
            // 访问条目属性信息时发生异常
            throw new InvalidObjectException(String.format(
                    "Problem creating %1$s from LDAP Entry %2$s",
                    this.mappedClass, object), ne);
        } catch (IllegalAccessException iae) {
            // 反射实例化对象或调用方法时发生异常
            throw new InvalidObjectException(String.format(
                    "Could not create an instance of %1$s or could not access field.",
                    this.mappedClass.getName()), iae);
        } catch (InstantiationException ie) {
            // 反射实例化对象时发生异常
            throw new InvalidObjectException(String.format(
                    "Could not instantiate %1$s.", this.mappedClass), ie);
        }
    }
}
